package com.mingserve.report.service;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.deepoove.poi.data.ChartMultiSeriesRenderData;
import com.deepoove.poi.data.SeriesRenderData;
import com.mingserve.common.core.text.Convert;
import com.mingserve.common.core.utils.DateUtils;
import com.mingserve.common.core.utils.HttpUtils;
import com.mingserve.common.core.utils.MathUtils;
import com.mingserve.common.core.utils.StringUtils;
import com.mingserve.common.core.utils.poi.WordUtils;
import com.mingserve.raw.domain.Equipment;
import com.mingserve.raw.domain.EquipmentConfig;
import com.mingserve.raw.domain.Point;
import com.mingserve.raw.domain.dto.SelectDto;
import com.mingserve.raw.domain.vo.RawVo;
import com.mingserve.raw.service.IEquipmentService;
import com.mingserve.raw.service.IPointService;
import com.mingserve.raw.service.IRawService;
import com.mingserve.report.domain.AlarmDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

@EnableAsync
@RequiredArgsConstructor
@Service
@Slf4j
public class AlarmService
{
    @Value("${server.api.url:''}")
    private String serverUrl;

    private final IEquipmentService equipmentService;
    private final IPointService pointService;
    private final IRawService rawService;

    /**
     * 创建报警信息报告
     */
    @Async
    public void createReport(AlarmDto alarmDto)
    {
        Equipment equipment = equipmentService.getEquipmentByCode(alarmDto.getEquipmentCode());
        if (equipment == null) {
            log.error("Not found alarm equipment");
            return;
        }
        Map<String, Object> map = new HashMap<>();
        map.put("createDate", DateUtils.getDate());
        map.put("equipment", equipment);
        map.put("equipmentHasImg", false);
        map.put("alarm", alarmDto);

        bindConfigInfo(map, alarmDto.getEquipmentCode());

        List<String> labelList = new ArrayList<>();

        Point point = pointService.getPointByCode(alarmDto.getPointCode());
        if (point != null) {
            try {
                bindPointChart(map, alarmDto.getPointTime(), point);
                bindPointFeature(map, labelList, alarmDto.getPointTime(), point);
            } catch (Exception ignored) { }
        }

        // component info
        // 存储图表标题对应的纵轴单位
        Map<String, String> titleMap = new HashMap<>();
        try {
            bindComponentInfo(map, titleMap, labelList, alarmDto);
        } catch (Exception ignored) {
        }

        String templatePath = "/template/alarm_type1_report.docx";
        byte[] fileByte = WordUtils.exportWordByte(templatePath, map, labelList, titleMap);
        String fileName = equipment.getEquipmentCode() + "_" + alarmDto.getAlarmTime() + ".docx";
        String folder = "raw/alarm" + "/" + DateUtils.datePath(alarmDto.getAlarmTime());

        Path directory = Paths.get(folder);
        Path filePath = directory.resolve(fileName);
        try {
            // 确保目录存在
            Files.createDirectories(directory);
            // 保存文件
            Files.write(filePath, fileByte);
            log.info("Alarm report create success {}", filePath.toAbsolutePath());
        } catch (IOException e) {
            log.error("Alarm report create failed ", e);
        }
    }

    /**
     * 绑定配置信息
     *
     * @param map
     * @param equipmentCode 设备级设备编码
     */
    void bindConfigInfo(Map<String, Object> map, String equipmentCode)
    {
        String prefix = equipmentCode.substring(0, 12);
        List<Equipment> equipmentList = equipmentService.queryList().stream().filter(l -> l.getEquipmentCode().contains(prefix)).toList();
        Equipment equip10 = equipmentList.stream().filter(l -> l.getLevel() == 10).findFirst().orElse(null);
        Equipment equip20 = equipmentList.stream().filter(l -> l.getLevel() == 20).findFirst().orElse(null);
        if (equip10 == null || equip20 == null) {
            log.error("Not found alarm equipment level equal 10 or 20");
            return;
        }
        String groupName = equip10.getEquipmentName() + "-" + equip20.getEquipmentName();
        map.put("groupName", groupName);

        // 测点列表
        List<Point> pointList = pointService.queryList().stream().filter(l -> l.getEquipmentCode().equals(equipmentCode)).toList();
        map.put("pointCount", pointList.size());
        Long minInstallTime = pointList.stream().map(Point::getInstallTime).min(Long::compareTo).orElse(null);
        // 监测天数
        Long monitorDays = DateUtils.pastDays(minInstallTime);
        map.put("monitorDays", monitorDays);

        // 监测配置
        EquipmentConfig equipmentConfig = equipmentService.getEquipmentConfigByCode(equipmentCode);
        if (equipmentConfig != null) {
            JSONObject configMap = JSON.parseObject(equipmentConfig.getConfig());
            if (configMap.containsKey("iphm_equipment_property")) {
                JSONObject equipmentProperty = configMap.getJSONObject("iphm_equipment_property");
                map.put("equipmentProperty", equipmentProperty);
                // 运行时长
                map.put("runningTime", monitorDays + equipmentProperty.getLong("usedDays"));
            }
            // 采集配置
            if (configMap.containsKey("iphm_sensor_point")) {
                JSONObject sensorConfig = configMap.getJSONObject("iphm_sensor_point");
                sensorConfig.put("sampleTime", MathUtils.round(sensorConfig.getDouble("SamplePoints") / sensorConfig.getDouble("VibFrequency"), 2));
                // 查询机组采样间隔
                EquipmentConfig equipmentConfig20 = equipmentService.getEquipmentConfigByCode(equip20.getEquipmentCode());
                if (equipmentConfig20 != null) {
                    JSONObject sensorConfig20 = JSON.parseObject(equipmentConfig20.getConfig());
                    if (sensorConfig20.containsKey("iphm_sensor_equipment")) {
                        JSONObject equipConfig = sensorConfig20.getJSONObject("iphm_sensor_equipment");
                        sensorConfig.put("SampleInterval", MathUtils.round(equipConfig.getInteger("SampleInterval") / 60.0, 2));
                    }
                }
                map.put("sensorConfig", sensorConfig);
            }
        }
    }

    /**
     * 绑定测点 raw + fft + rms Charts
     *
     * @param map
     * @param pointTime
     * @param point
     */
    void bindPointChart(Map<String, Object> map, Long pointTime, Point point) throws IOException
    {
        Map<String, ChartMultiSeriesRenderData> chartMap = new HashMap<>();

        // 测点轴向
        JSONObject axialMap = JSON.parseObject(point.getAxial());

        SelectDto query = new SelectDto();
        query.setPointCode(point.getPointCode());
        query.setEventTime(pointTime);
        query.setAxial("xyz");
        query.setPhysicalType("acc");
        int sampleSize = 9000;
        RawVo rawAcc = rawService.getVibRaw(query);
        if (rawAcc != null && rawAcc.getT() != null) {
            dataResampling(rawAcc, sampleSize);
            chartMap.put("pointAccChart", createWaveChart(rawAcc, point.getPointName() + "振动加速度", axialMap));
        } else {
            log.error("Not found rawAcc data {}_{}", point.getPointCode(), pointTime);
        }
        RawVo rawAccFFT = rawService.getVibFFT(query);
        if (rawAccFFT != null && rawAccFFT.getT() != null) {
            dataResampling(rawAccFFT, sampleSize);
            chartMap.put("pointAccFFTChart", createWaveChart(rawAccFFT, point.getPointName() + "振动加速度FFT", axialMap));
        }
        query.setPhysicalType("vel");
        RawVo rawVel = rawService.getVibRaw(query);
        if (rawVel != null && rawVel.getT() != null) {
            dataResampling(rawVel, sampleSize);
            chartMap.put("pointVelChart", createWaveChart(rawVel, point.getPointName() + "振动速度", axialMap));
        }
        RawVo rawVelFFT = rawService.getVibFFT(query);
        if (rawVelFFT != null && rawVelFFT.getT() != null) {
            dataResampling(rawVelFFT, sampleSize);
            chartMap.put("pointVelFFTChart", createWaveChart(rawVelFFT, point.getPointName() + "振动速度FFT", axialMap));
        }

        // rms 趋势Charts
//        long beginTime = alarmTime - 7 * DateUtils.MILLIS_PER_DAY * 1000;
//        String url = serverUrl + "/api/freq/queryFeature?pointCode=" + point.getPointCode() + "&params[beginTime]=" + DateUtils.formatDate(beginTime, DateUtils.YYYY_MM_DD) + "&params[endTime]=" + DateUtils.formatDate(alarmTime, DateUtils.YYYY_MM_DD) + "&featureType=rms&physicalType=";
        String url = serverUrl + "/api/freq/queryFeature?pointCode=" + point.getPointCode() + "&eventTime=" + pointTime + "&featureType=rms&physicalType=";

        String accFeature = HttpUtils.sendGet(url + "acc");
        JSONObject accFeatureJson = JSON.parseObject(accFeature);
        ChartMultiSeriesRenderData accRmsChart = createFeatureChart(accFeatureJson.getJSONObject("data"), point.getPointName() + "振动加速度特征", axialMap);
        if (accRmsChart != null)
            chartMap.put("pointAccRmsChart", accRmsChart);
        else
            log.error("Not found rawAccRms list {}_{}", point.getPointCode(), pointTime);

        String velFeature = HttpUtils.sendGet(url + "vel");
        JSONObject velFeatureJson = JSON.parseObject(velFeature);
        ChartMultiSeriesRenderData velRmsChart = chartMap.put("pointVelRmsChart", createFeatureChart(velFeatureJson.getJSONObject("data"), point.getPointName() + "振动速度特征", axialMap));
        if (velRmsChart != null)
            chartMap.put("pointVelRmsChart", velRmsChart);

        // point raw + fft + rms Charts
        map.putAll(chartMap);
    }

    /**
     * 生成测点波形chart
     *
     * @param data
     * @param title
     * @param axialMap
     * @return
     */
    ChartMultiSeriesRenderData createWaveChart(RawVo data, String title, JSONObject axialMap)
    {
        if (data == null || data.getT() == null) {
            return null;
        }
        String[] timeArray = Arrays.stream(data.getT()).mapToObj(value -> String.format("%.2f", value)).toArray(String[]::new);
        List<SeriesRenderData> seriesDataList = new ArrayList<>();
        if (data.getX() != null && data.getX().length == timeArray.length) {
            seriesDataList.add(new SeriesRenderData("无效".equals(axialMap.getString("x")) ? "X" : axialMap.getString("x"), Convert.toNumberArray(data.getX())));
        }
        if (data.getY() != null && data.getY().length == timeArray.length) {
            seriesDataList.add(new SeriesRenderData("无效".equals(axialMap.getString("y")) ? "Y" : axialMap.getString("y"), Convert.toNumberArray(data.getY())));
        }
        if (data.getZ() != null && data.getZ().length == timeArray.length) {
            seriesDataList.add(new SeriesRenderData("无效".equals(axialMap.getString("z")) ? "Z" : axialMap.getString("z"), Convert.toNumberArray(data.getZ())));
        }

        ChartMultiSeriesRenderData chart = new ChartMultiSeriesRenderData();
        chart.setChartTitle(title);
        chart.setCategories(timeArray);
        chart.setSeriesDatas(seriesDataList);

        return chart;
    }

    /**
     * 生成测点特征chart
     *
     * @param rmsFeature
     * @param title
     * @param axialMap
     * @return
     */
    ChartMultiSeriesRenderData createFeatureChart(JSONObject rmsFeature, String title, JSONObject axialMap)
    {
        if (rmsFeature.getJSONArray("eventTime").isEmpty()) return null;

        List<SeriesRenderData> seriesRenderData = new ArrayList<>();
        List<String> timeList = new ArrayList<>();
        for (String key : axialMap.keySet()) {
            if ("无效".equals(axialMap.get(key))) {
                continue;
            }
            seriesRenderData.add(new SeriesRenderData(axialMap.getString(key), rmsFeature.getJSONArray(key).toArray(Number[]::new)));
            if (StringUtils.isEmpty(timeList)) {
                timeList = rmsFeature.getJSONArray("eventTime").stream().map(l -> DateUtils.formatMonthDate(DateUtils.getTimestamp(l.toString()))).toList();
            }
        }
        ChartMultiSeriesRenderData chart = new ChartMultiSeriesRenderData();
        chart.setChartTitle(title);
        chart.setCategories(timeList.toArray(new String[0]));
        chart.setSeriesDatas(seriesRenderData);
        return chart;
    }

    /**
     * 绑定测点特征值表格 feature table
     *
     * @param map
     * @param labelList
     * @param pointTime
     * @param point
     * @throws IOException
     */
    void bindPointFeature(Map<String, Object> map, List<String> labelList, Long pointTime, Point point) throws IOException
    {
        Map<String, Object> featureMap = new HashMap<>();

        String url = serverUrl + "/api/freq/getFeature?pointCode=" + point.getPointCode() + "&eventTime=" + pointTime;
        String resFeature = HttpUtils.sendGet(url);
        JSONObject featureJson = JSON.parseObject(resFeature);
        if (!featureJson.containsKey("data")) {
            return;
        }
        featureMap.put("title", point.getPointName() + "最新时域特征值");
        featureMap.put("featureTime", DateUtils.formatDate(pointTime, DateUtils.YYYY_MM_DD_HH_MM_SS));
        // 测点轴向
        JSONObject axialMap = JSON.parseObject(point.getAxial());
        for (Map.Entry<String, Object> entry : axialMap.entrySet()) {
            if ("无效".equals(entry.getValue())) {
                entry.setValue(entry.getKey().toUpperCase());
            }
        }
        featureMap.put("axial", axialMap);
        JSONObject obj = featureJson.getJSONObject("data");

        // 时域特征表格数据
        List<Map<String, Object>> valuesList = new ArrayList<>();
        for (Map.Entry<String, String> entry : FEATURE_MAP.entrySet()) {
            Map<String, Object> featureValues = new HashMap<>();
            featureValues.put("featureType", entry.getValue());
            JSONObject feature = obj.getJSONObject(entry.getKey());
            for (String axial : axialMap.keySet()) {
                featureValues.put("acc" + axial.toUpperCase(), MathUtils.round(feature.getDouble("acc_" + axial), 3));
                featureValues.put("vel" + axial.toUpperCase(), MathUtils.round(feature.getDouble("vel_" + axial), 3));
            }
            valuesList.add(featureValues);
        }
        featureMap.put("featureValues", valuesList);

        if (!valuesList.isEmpty()) {
            map.putAll(featureMap);
            labelList.add("featureValues");
        } else {
            log.error("Not found point feature values {}_{}", point.getPointCode(), pointTime);
        }
    }

    static final HashMap<String, String> FEATURE_MAP = new HashMap<>() {{
        put("rms", "有效值");
        put("kurt", "峭度");
        put("peak", "峰峰值");
        put("skew", "偏度");
        put("shape_factor", "波形因子");
        put("crest_factor", "峰值因子");
        put("impulse_factor", "脉冲因子");
        put("clearance_factor", "裕度");
        put("avg_abs", "绝对平均值");
    }};

    /**
     * 数据重采样
     *
     * @param rawVo
     * @param sampleSize
     */
    void dataResampling(RawVo rawVo, int sampleSize)
    {
        if (rawVo == null || rawVo.getT() == null) {
            log.info("Raw data is empty");
            return;
        }
        double[] t = rawVo.getT();
        int dataLength = t.length;
        if (dataLength > sampleSize)
        {
            double[] x = rawVo.getX();
            double[] y = rawVo.getY();
            double[] z = rawVo.getZ();
            int n = (int) Math.ceil((double) dataLength / sampleSize); // 向上取整
            int newLength = (int) Math.ceil((double) dataLength / n);
            // 创建新的数组
            double[] newX = (x != null && x.length > 0) ? new double[newLength] : null;
            double[] newY = (y != null && y.length > 0) ? new double[newLength] : null;
            double[] newZ = (z != null && z.length > 0) ? new double[newLength] : null;
            double[] newt = new double[newLength];

            // 进行重采样
            for (int i = 0; i < newLength; i++) {
                if (newX != null) {
                    newX[i] = x[i * n];
                }
                if (newY != null) {
                    newY[i] = y[i * n];
                }
                if (newZ != null) {
                    newZ[i] = z[i * n];
                }
                newt[i] = t[i * n];
            }

            // 更新原始数组
            if (newX != null) {
                x = newX;
            }
            if (newY != null) {
                y = newY;
            }
            if (newZ != null) {
                z = newZ;
            }
            t = newt;
            // 更新 RawVo 对象中的数组
            rawVo.setX(x);
            rawVo.setY(y);
            rawVo.setZ(z);
            rawVo.setT(t);
        }
    }

    /**
     * 绑定设备信息
     *
     * @param map
     * @param titleMap
     * @param labelList
     * @param alarmDto
     * @throws IOException
     */
    void bindComponentInfo(Map<String, Object> map, Map<String, String> titleMap, List<String> labelList, AlarmDto alarmDto) throws IOException {
        String url = serverUrl + "/api/freq/getAlarmDetail?equipmentCode=" + alarmDto.getEquipmentCode() + "&eventTime=" + alarmDto.getAlarmTime();

        String res = HttpUtils.sendGet(url);
        JSONObject resJson = JSON.parseObject(res);
        JSONArray freqList = resJson.getJSONArray("data");

        List<Map<String, ChartMultiSeriesRenderData>> chartList = new ArrayList<>();
        StringJoiner sb = new StringJoiner("、");
        for (Object item : freqList) {
            JSONObject obj = (JSONObject) item;
            sb.add(obj.getString("componentName"));
            String physicalName = obj.getString("physicalType").equals("acc") ? "加速度" : "速度";
            String title = obj.getString("pointName") + obj.getString("failureName") + "振动" + physicalName + obj.getString("ampName") + "融合值趋势";
            if ("加速度".equals(physicalName)) {
                titleMap.put(title, "g");
            } else {
                titleMap.put(title, "mm/s");
            }

            JSONObject axial = obj.getJSONObject("axial");
            List<SeriesRenderData> seriesRenderData = new ArrayList<>();
            List<String> timeList;
            timeList = obj.getJSONArray("ampTime").stream().map(l -> DateUtils.formatMonthDate(Convert.toLong(l))).toList();
            Number[] numbersX = obj.getJSONArray("ampX").stream().map(Convert::toBigDecimal).toArray(Number[]::new);
            if (timeList.size() == numbersX.length) {
                seriesRenderData.add(new SeriesRenderData(axial.getString("x"), numbersX));
            }
            Number[] numbersY = obj.getJSONArray("ampY").stream().map(Convert::toBigDecimal).toArray(Number[]::new);
            if (timeList.size() == numbersY.length) {
                seriesRenderData.add(new SeriesRenderData(axial.getString("y"), numbersY));
            }
            Number[] numbersZ = obj.getJSONArray("ampZ").stream().map(Convert::toBigDecimal).toArray(Number[]::new);
            if (timeList.size() == numbersZ.length) {
                seriesRenderData.add(new SeriesRenderData(axial.getString("z"), numbersZ));
            }

            //生成频域acc、vel图表对象
            Map<String, ChartMultiSeriesRenderData> chartMap = new HashMap<>();
            ChartMultiSeriesRenderData chartFreq = new ChartMultiSeriesRenderData();
            chartFreq.setChartTitle(title);
            chartFreq.setCategories(timeList.toArray(new String[0]));
            chartFreq.setSeriesDatas(seriesRenderData);
            chartMap.put("freqChart", chartFreq);
            chartList.add(chartMap);
        }

        map.put("rawFFTCharts", chartList);
        labelList.add("rawFFTCharts");
        map.put("componentNamesAll", sb.toString());
    }
}
